#region File Description
//-----------------------------------------------------------------------------
// GameplayScreen.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
using Microsoft.Devices.Sensors;
#endregion

namespace IcyTowerMobile
{
	/// <summary>
	/// This screen implements the actual game logic.
	/// </summary>
	class GameplayScreen : GameScreen
	{
		#region Fields

		/// <summary>
		/// Start speed of the tower background.
		/// </summary>
		public const int START_SPEED_BACKGROUND = 1;
		/// <summary>
		/// Start speed of the platforms as soon as the user starts moving.
		/// </summary>
		public const int START_SPEED_PLATFORMS = 2;
		/// <summary>
		/// Start speed of the tower walls on the left and right side.
		/// </summary>
		public const int START_SPEED_WALLS = 3;

		/// <summary>
		/// Number of platforms which are visible at once.
		/// </summary>
		public const int NR_PLATFORMS_PER_SCREEN = 9;
		/// <summary>
		/// Vertical distance between platforms.
		/// </summary>
		public const int Y_DISTANCE_BETWEEN_PLATFORMS = 85;
		/// <summary>
		/// Number of platforms the user has to pass in order for game scrolling starting.
		/// </summary>
		public const int NR_PLATFORMS_SCROLLING_STARTS = 5;

		/// <summary>
		/// Interval in milliseconds when the hurry up message should come
		/// and the game speed is increased.
		/// </summary>
		private const int HURRY_UP_EVERY = 60000;
		/// <summary>
		/// Duration in milliseconds of the hurry up text to show up on screen.
		/// </summary>
		private const int HURRY_UP_DURATION = 3000;
		/// <summary>
		/// Factor for the score to be multiplied with.
		/// </summary>
		private const int HIGHSCORE_FACTOR = 10;

		// content manager
		private ContentManager content;
		private Rectangle titleSafeArea;

		// game objects
		private Player player;
		private BackgroundBrick mainBrick;
		private BackgroundBrick leftForegroundBrick;
		private BackgroundBrick rightForegroundBrick;
		private List<Platform> platformList;

		// resources
		private Texture2D leftTexture;
		private Texture2D middleTexture;
		private Texture2D rightTexture;
		private Song gameBackgroundSong;
		private SoundEffect falling;

		// sensor
		private Accelerometer accelerometerSensor;
		private Vector3 accelerometerReading;

		// helper variables
		private long time;
		private int timebuffer;
		private int currentScore;
		private bool userSoundChecked;
		private bool gameOver;
		private bool isPlayerHighEnough;
		private bool showHurryUp;

		#endregion

		#region Initialization

		/// <summary>
		/// Constructor. Initializes the game objects and starts listening
		/// for accelerometer values.
		/// </summary>
		public GameplayScreen()
		{
			TransitionOnTime = TimeSpan.FromSeconds(1.5);
			TransitionOffTime = TimeSpan.FromSeconds(0.5);

			// initialize game objects
			player = new Player();
			mainBrick = new BackgroundBrick();
			leftForegroundBrick = new BackgroundBrick();
			rightForegroundBrick = new BackgroundBrick();
			platformList = new List<Platform>();

			accelerometerReading = new Vector3();

			// Add the accelerometer event handler to the accelerometer sensor.
			accelerometerSensor = new Accelerometer();
			accelerometerSensor.CurrentValueChanged += CurrentValueChanged;
			accelerometerSensor.Start();
		}


		/// <summary>
		/// Load graphics content for the game.
		/// </summary>
		public override void LoadContent()
		{
			if (content == null)
				content = new ContentManager(ScreenManager.Game.Services, "Content");

			titleSafeArea = ScreenManager.GraphicsDevice.Viewport.TitleSafeArea;

			// load sounds and sound effects
			gameBackgroundSong = content.Load<Song>("sounds/bg_beat");
			falling = content.Load<SoundEffect>("sounds/falling");

			// load platform textures
			leftTexture = content.Load<Texture2D>("platforms/platform1_left");
			middleTexture = content.Load<Texture2D>("platforms/platform1_middle");
			rightTexture = content.Load<Texture2D>("platforms/platform1_right");

			// load background texture
			Texture2D backgroundTexture = content.Load<Texture2D>("textures/texture_background");
			mainBrick.Initialize(content, backgroundTexture, titleSafeArea.Height, 0, false, START_SPEED_BACKGROUND);

			// load left tower wall background
			Texture2D foregroundTextureLeft = content.Load<Texture2D>("textures/texture_foreground_tower_left");
			leftForegroundBrick.Initialize(content, foregroundTextureLeft, titleSafeArea.Height, 0, true, START_SPEED_WALLS);

			// load right tower wall background
			Texture2D foregroundTextureRight = content.Load<Texture2D>("textures/texture_foreground_tower_right");
			rightForegroundBrick.Initialize(content, foregroundTextureRight, titleSafeArea.Height, titleSafeArea.Width - foregroundTextureRight.Width, false, START_SPEED_WALLS);

			// load player sprite strips for animations
			Texture2D playerIdleSpriteStrip = content.Load<Texture2D>("sprites/sprite_harold_states_standing");
			Texture2D playerRunRightSpriteStrip = content.Load<Texture2D>("sprites/sprite_harold_states_running_right");
			Texture2D playerRunLeftSpriteStrip = content.Load<Texture2D>("sprites/sprite_harold_states_running_left");
			Texture2D playerJumpStraight = content.Load<Texture2D>("sprites/sprite_harold_states_jumping_straight");
			Texture2D playerJumpRight = content.Load<Texture2D>("sprites/sprite_harold_states_jumping_right");
			Texture2D playerJumpLeft = content.Load<Texture2D>("sprites/sprite_harold_states_jumping_left");
			Texture2D playerJumpRotate = content.Load<Texture2D>("sprites/sprite_harold_states_rotating");

			// initialize animations
			List<Animation> animationTextureList = new List<Animation>();
			Vector2 playerPosition = new Vector2(titleSafeArea.Width / 2, titleSafeArea.Height - Math.Max(rightTexture.Height, Math.Max(leftTexture.Height, middleTexture.Height)) - 100);

			Animation a = new Animation();
			a.Initialize(playerIdleSpriteStrip, 42, playerIdleSpriteStrip.Height, 4, 200, Color.White, 1, true);
			animationTextureList.Add(a);

			a = new Animation();
			a.Initialize(playerRunRightSpriteStrip, 42, playerRunRightSpriteStrip.Height, 5, 200, Color.White, 1, true);
			animationTextureList.Add(a);

			a = new Animation();
			a.Initialize(playerRunLeftSpriteStrip, 42, playerRunLeftSpriteStrip.Height, 5, 200, Color.White, 1, true);
			animationTextureList.Add(a);

			a = new Animation();
			a.Initialize(playerJumpStraight, 42, playerJumpStraight.Height, 1, 200, Color.White, 1, true);
			animationTextureList.Add(a);

			a = new Animation();
			a.Initialize(playerJumpRight, 42, playerJumpRight.Height, 1, 200, Color.White, 1, true);
			animationTextureList.Add(a);

			a = new Animation();
			a.Initialize(playerJumpLeft, 42, playerJumpLeft.Height, 1, 200, Color.White, 1, true);
			animationTextureList.Add(a);

			a = new Animation();
			a.Initialize(playerJumpRotate, 60, playerJumpRotate.Height, 1, 200, Color.White, 1, true, true);
			animationTextureList.Add(a);

			// initialize player and load content
			player.Initialize(animationTextureList, playerPosition);
			player.LoadContent(content);

			// generate platforms
			for (int i = 0; i < NR_PLATFORMS_PER_SCREEN; i++)
			{
				Platform p = new Platform();
				p.Initialize(leftTexture, middleTexture, rightTexture,
					titleSafeArea.Height - leftTexture.Height - i * Y_DISTANCE_BETWEEN_PLATFORMS,
					titleSafeArea.Height, leftForegroundBrick.Width,
					titleSafeArea.Width - rightForegroundBrick.Width, i, START_SPEED_PLATFORMS);
				platformList.Add(p);
			}

			// once the load has finished, we use ResetElapsedTime to tell the game's
			// timing mechanism that we have just finished a very long frame, and that
			// it should not try to catch up.
			ScreenManager.Game.ResetElapsedTime();
		}

		/// <summary>
		/// Unload graphics content used by the game.
		/// </summary>
		public override void UnloadContent()
		{
			content.Unload();
		}

		#endregion

		#region Update and Draw


		/// <summary>
		/// Updates the state of the game. This method checks the GameScreen.IsActive
		/// property, so the game will stop updating when the pause menu is active,
		/// or if you tab away to a different application.
		/// </summary>
		/// <param name="gameTime">Provides a snapshot of timing values.</param>
		public override void Update(GameTime gameTime, bool otherScreenHasFocus,
													   bool coveredByOtherScreen)
		{
			base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);

			if (IsActive && !gameOver)
			{
				if (!userSoundChecked)
				{
					// start background music the very first time this method is called
					userSoundChecked = true;
					startBackgroundMusic();
				}

				// measure passed time for hurry up messages
				time += gameTime.ElapsedGameTime.Milliseconds;

				Vector2 v = player.Velocity;
				Vector2 playerPosition = player.PlayerPosition;

				if (playerPosition.Y < NR_PLATFORMS_SCROLLING_STARTS * Y_DISTANCE_BETWEEN_PLATFORMS)
				{
					// check if user is already high enough to start game scrolling
					isPlayerHighEnough = true;
				}

				// compute acceleration for game objects movement if player comes nearly on top of view
				int distance = 400 - (int)playerPosition.Y;
				double acceleration = distance / (0.5 * gameTime.ElapsedGameTime.TotalSeconds);
				acceleration = acceleration / 2000;
				int movement = acceleration > 0 ? (int)acceleration : 0;

				if ((int)time / HURRY_UP_EVERY > timebuffer)
				{
					// hurry up time has passed, increase game speed
					timebuffer = (int)time / HURRY_UP_EVERY;
					mainBrick.increaseSpeed();
					leftForegroundBrick.increaseSpeed();
					rightForegroundBrick.increaseSpeed();
					foreach (var platform in platformList)
					{
						platform.IncreaseSpeed();
					}
					showHurryUp = true;
				}
				if (showHurryUp && time % HURRY_UP_EVERY > HURRY_UP_DURATION)
				{
					// hurry up duration has passed
					showHurryUp = false;
				}

				// update game objects
				mainBrick.Update(gameTime, movement, isPlayerHighEnough);
				leftForegroundBrick.Update(gameTime, movement, isPlayerHighEnough);
				rightForegroundBrick.Update(gameTime, movement, isPlayerHighEnough);

				foreach (Platform p in platformList)
				{
					p.Update(gameTime, movement, isPlayerHighEnough);
				}

				TouchCollection touches = TouchPanel.GetState();
				player.Update(gameTime, movement, accelerometerReading.X, touches.Count);

				// check collisions
				player.checkBrickIntersect(leftForegroundBrick);
				player.checkBrickIntersect(rightForegroundBrick);
				Platform intersectedPlatform = player.checkPlatformIntersect(platformList);

				if (intersectedPlatform != null)
				{
					// update current score
					currentScore = intersectedPlatform.Value;
				}

				// update player animation
				player.UpdateAnimation(gameTime);

				if (player.PlayerPosition.Y - player.Height > ScreenManager.GraphicsDevice.Viewport.Height)
				{
					// player has fallen off from screen, exit game
					if (IsolatedStorageManager.GetInstance().IsSoundEnabled() && MediaPlayer.GameHasControl)
						falling.Play(1, 0, 0);
					gameOver = true;
					SaveHighScore();
					MessageBoxScreen messageBox = new MessageBoxScreen("Game Over!\n\nYour Score: " + currentScore * HIGHSCORE_FACTOR);
					messageBox.Accepted += HighscoreBoxAccepted;
					ScreenManager.AddScreen(messageBox, null);
				}
			}
		}

		/// <summary>
		/// Event handler method which is called when the user presses the OK button in game over dialog.
		/// Just load main menu screen.
		/// </summary>
		void HighscoreBoxAccepted(object sender, PlayerIndexEventArgs e)
		{
			LoadingScreen.Load(ScreenManager, false, ControllingPlayer, new BackgroundScreen(), new MainMenuScreen());
		}

		/// <summary>
		/// Starts game background music.
		/// </summary>
		private void startBackgroundMusic()
		{
			if (IsolatedStorageManager.GetInstance().IsSoundEnabled())
			{
				FrameworkDispatcher.Update();
				if (MediaPlayer.GameHasControl)
				{
					MediaPlayer.Stop();
					MediaPlayer.IsRepeating = true;
					MediaPlayer.Volume = 0.3f;
					MediaPlayer.Play(gameBackgroundSong);
				}
			}
		}

		/// <summary>
		/// Lets the game respond to player input. Unlike the Update method,
		/// this will only be called when the gameplay screen is active.
		/// </summary>
		public override void HandleInput(InputState input)
		{
			if (input == null)
				throw new ArgumentNullException("input");

			// Look up inputs for the active player profile.
			int playerIndex = (int)ControllingPlayer.Value;

			KeyboardState keyboardState = input.CurrentKeyboardStates[playerIndex];
			GamePadState gamePadState = input.CurrentGamePadStates[playerIndex];

			// if the user pressed the back button, we return to the main menu
			PlayerIndex player;
			if (input.IsNewButtonPress(Buttons.Back, ControllingPlayer, out player))
			{
				LoadingScreen.Load(ScreenManager, false, ControllingPlayer, new BackgroundScreen(), new MainMenuScreen());
			}
		}

		/// <summary>
		/// Event handler method which is called when a new value is available at the accelerometer.
		/// </summary>
		public void CurrentValueChanged(object sender, SensorReadingEventArgs<AccelerometerReading> e)
		{
			accelerometerReading.X = (float)e.SensorReading.Acceleration.X;
		}

		/// <summary>
		/// Draws the gameplay screen.
		/// </summary>
		/// <param name="gameTime">Provides a snapshot of timing values.</param> 
		public override void Draw(GameTime gameTime)
		{
			// clear screen
			ScreenManager.GraphicsDevice.Clear(ClearOptions.Target, Color.CornflowerBlue, 0, 0);

			SpriteBatch spriteBatch = ScreenManager.SpriteBatch;
			spriteBatch.Begin();

			// draw tower background
			mainBrick.Draw(spriteBatch);

			// draw platforms
			foreach (Platform p in platformList)
			{
				p.Draw(spriteBatch);
			}

			// draw player
			player.Draw(spriteBatch);

			// draw tower walls
			leftForegroundBrick.Draw(spriteBatch);
			rightForegroundBrick.Draw(spriteBatch);

			// draw current score
			spriteBatch.DrawString(ScreenManager.AboutFont, "Score: " + currentScore * HIGHSCORE_FACTOR, new Vector2(10, 10), Color.White);

			if (showHurryUp)
			{
				// draw hurry up
				String hurryUp = "Hurry Up!";
				SpriteFont icyTowerFont = ScreenManager.IcyTowerFont;
				Vector2 text = icyTowerFont.MeasureString(hurryUp);
				Vector2 textPos = new Vector2((ScreenManager.GraphicsDevice.Viewport.Width - text.X) / 2, (ScreenManager.GraphicsDevice.Viewport.Height - text.Y) / 2);
				spriteBatch.DrawString(ScreenManager.IcyTowerFont, hurryUp, textPos, Color.White);
			}

			spriteBatch.End();

			// If the game is transitioning on or off, fade it out to black.
			if (TransitionPosition > 0)
				ScreenManager.FadeBackBufferToBlack(1f - TransitionAlpha);
		}

		/// <summary>
		/// Persists the current score as high score if it is high enough.
		/// </summary>
		private void SaveHighScore()
		{
			// Create the data to save
			HighscoreData data = IsolatedStorageManager.GetInstance().LoadHighScores();

			int scoreIndex = -1;
			for (int i = 0; i < data.Count; i++)
			{
				if (currentScore * HIGHSCORE_FACTOR > data.Score[i])
				{
					// index where to insert current score has been found
					scoreIndex = i;
					break;
				}
			}

			if (scoreIndex > -1)
			{
				// new high score found ... do swaps
				for (int i = data.Count - 1; i > scoreIndex; i--)
				{
					data.PlayerName[i] = data.PlayerName[i - 1];
					data.Score[i] = data.Score[i - 1];
					data.Level[i] = data.Level[i - 1];
				}

				data.PlayerName[scoreIndex] = DateTime.Now.ToShortDateString();
				data.Score[scoreIndex] = currentScore * HIGHSCORE_FACTOR;
				data.Level[scoreIndex] = currentScore;

				// persist modified highscore list
				IsolatedStorageManager.GetInstance().SaveHighScores(data);
			}
		}

		#endregion
	}
}
